package simulation;

import assembly.Assembly;
import assembly.Mass;
import assembly.Muscle;
import assembly.Spring;
import force.CenterOfMass;
import force.Force;
import force.Gravity;
import force.Viscosity;
import force.WallRepulsion;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import util.Sprite;


/**
 * Responsible for the instantiation and addition logic of new objects to the model.
 * 
 * @author Robert C. Duvall
 * @author Dagbedji Fagnisse
 * @author Scott Valentine
 */
public class Factory {

    /**
     * static instance of factory, used for any creation such that we only use one instance
     * of factory
     */
    private static Factory ourInstance;

    private Map<String, ILoadable> myRegisteredLoadables = new HashMap<String, ILoadable>();

    /**
     * adds all of loadable elements to registerLoadable
     * to be used in the creation of additional loaded elements
     */
    public Factory () {
        super();
        registerLoadable(new Gravity());
        registerLoadable(new Viscosity());
        registerLoadable(new CenterOfMass());
        registerLoadable(new WallRepulsion());

        registerLoadable(new Mass());
        registerLoadable(new Spring());
        registerLoadable(new Muscle());
    }

    /**
     * Get the unique instance of Factory available.
     * 
     * @return the current factory.
     */
    public static synchronized Factory getInstance () {
        // TODO: Decide on whether Factory will be a singleton or not.
        if (ourInstance == null) {
            ourInstance = new Factory();
        }
        return ourInstance;
    }

    /**
     * reads in data from file and creates assembly from data to return
     * 
     * @param modelFile is the file to read in assembly data from
     * @return an assembly
     */
    public Assembly loadAssembly (File modelFile) {
        Assembly thisAssembly = new Assembly();
        try {
            Scanner input = new Scanner(modelFile);
            while (input.hasNext()) {
                Scanner line = new Scanner(input.nextLine());
                if (line.hasNext()) {
                    String type = line.next();

                    thisAssembly.add((Sprite) myRegisteredLoadables.get(type).load(line,
                                                                                   thisAssembly));

                    // we are ignoring the warning here since the repeated code is the catch
                    // exception
                }
            }
            input.close();
        }
        catch (FileNotFoundException e) {
            // should not happen because File came from user selection
            e.printStackTrace();
        }
        return thisAssembly;
    }

    /**
     * loads all of the forces from file or from default and returns list of forces
     * 
     * @param model - model that forces will be passed into
     * @param forcesFile - file to read forces from
     * @return - list of all possible forces (read-in and default)
     */
    public List<Force> loadForces (Model model, File forcesFile) {
        List<Force> theseForces = new ArrayList<Force>();
        try {
            Scanner input = new Scanner(forcesFile);
            while (input.hasNext()) {
                Scanner line = new Scanner(input.nextLine());
                if (line.hasNext()) {
                    String type = line.next();
                    // throw exception here as well
                    theseForces.add((Force) ((myRegisteredLoadables.get(type))).load(line, model));
                }
            }
            input.close();
        }
        catch (FileNotFoundException e) {
            // should not happen because File came from user selection
            e.printStackTrace();
        }
        addUncalledForces(theseForces);
        return theseForces;
    }

    /**
     * adds forces that were not specified in the load in file
     * 
     * @param forces - the forces that were loaded in from the file and that will have the default
     *        values for unloaded added to it
     */
    private void addUncalledForces (List<Force> forces) {
        for (String il : myRegisteredLoadables.keySet()) {
            ILoadable loadedEl = myRegisteredLoadables.get(il);
            if (loadedEl instanceof Force) {
                appendDefaultForces((Force) loadedEl, forces);
            }
        }
    }

    /**
     * checks if a force is in the list of forces, if it isn't then append the list with the force
     * 
     * @param force to be added if not in list of forces
     * @param currentForces is the list of forces that force will be appended too as needed.
     *        TODO: we could make the force list a set since there is no order nor any reason to
     *        have more than two instance of a force
     */
    private void appendDefaultForces (Force force, List<Force> currentForces) {
        boolean isIn = false;
        for (Force f : currentForces) {
            isIn = isIn || (force.getClass().equals(f.getClass()));
        }
        if (!isIn) {
            currentForces.add(force);
        }
    }

    private void registerLoadable (ILoadable l) {
        myRegisteredLoadables.put(l.getKeyword(), l);
    }

}
